package at.fhv.androideightpuzzle.application;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import at.fhv.androideightpuzzle.application.interfaces.IPlayPuzzleController;
import at.fhv.androideightpuzzle.application.interfaces.ISelectPuzzleController;
import at.fhv.androideightpuzzle.domain.FinishedPlayer;
import at.fhv.androideightpuzzle.domain.Position;
import at.fhv.androideightpuzzle.domain.SwapDirection;
import at.fhv.androideightpuzzle.network.ConnectionType;
import at.fhv.androideightpuzzle.network.NetworkServerSocket;
import at.fhv.androideightpuzzle.network.NetworkSocket;
import at.fhv.androideightpuzzle.network.NetworkSocketFactory;
import at.fhv.androideightpuzzle.network.message.GameResultMessage;
import at.fhv.androideightpuzzle.network.message.IMessage;
import at.fhv.androideightpuzzle.network.message.JoinGameMessage;
import at.fhv.androideightpuzzle.network.message.PuzzleSolvedMessage;
import at.fhv.androideightpuzzle.network.message.StartPuzzleMessage;

/**
 * <code>MultiPlayerHostController</code> is the host in the multi player game.
 * @author psp5448
 *
 */
public class MultiPlayerHostController extends Observable implements IPlayPuzzleController,ISelectPuzzleController,Observer{
	
	private static MultiPlayerHostController m_instance;
	
	private PlayPuzzleController m_playPuzzleController;
	private SelectPuzzleController m_selectPuzzleController;
	
	private NetworkServerSocket m_serverSocket;
	
	private List<String> m_playerNames;
	private List<FinishedPlayer> m_finishedPlayers;
	
	private MultiPlayerHostController(){
		m_playPuzzleController=new PlayPuzzleController();
		m_selectPuzzleController=new SelectPuzzleController();
		
		m_serverSocket=null;
		
		m_playerNames=new ArrayList<String>();
		m_finishedPlayers=new ArrayList<FinishedPlayer>();
	}
	
	public static MultiPlayerHostController getInstance(){
		if(m_instance==null){
			m_instance=new MultiPlayerHostController();
		}
		
		return m_instance;
	}
	
	public static void disposeInstance(){
		if(m_instance!=null){
			//dispose logic
			m_instance.deleteObservers();
			
			try{
				m_instance.m_serverSocket.closeSockets();
			}catch(Exception exc){}
			
			m_instance=null;
		}
	}
	
	public static boolean hasInstance(){
		if(m_instance!=null){
			return true;
		}else{
			return false;
		}
	}
	
	public void acceptClients(ConnectionType type){
		m_serverSocket=NetworkSocketFactory.createNetworkServerSocket(type);
		m_serverSocket.addObserver(this);
		
		m_playerNames.add("The Host");
		
		try{
			m_serverSocket.startAccepting();
		}catch(IOException exc){
			android.util.Log.e("MultiplayerHostController","An exception occurred: "+exc.getMessage(),exc);
		}
	}
	
	public void stopAcceptingClients(){
		if(m_serverSocket!=null){
			try{
				m_serverSocket.stopAccepting();
				
				/*for(NetworkSocket socket:m_serverSocket.getClients()){
					socket.addObserver(this);
					socket.deleteObserver(m_serverSocket);
				}*/
			}catch(IOException exc){
				android.util.Log.e("MultiplayerHostController","An exception occurred: "+exc.getMessage(),exc);
			}
		}
	}
	
	public String[] getClientNames(){
		return m_playerNames.toArray(new String[0]);
	}
	
	private void sendMessage(IMessage message){
		if(message!=null){
			android.util.Log.d("MultiplayerHostController", "Start to send message");
			for(NetworkSocket socket:m_serverSocket.getClients()){
				try{
					android.util.Log.d("MultiplayerHostController", "send message to client");
					socket.sendMessage(message);
				}catch(IOException exc){
					android.util.Log.e("MultiplayerHostController",exc.getMessage(),exc);
				}
			}
			
			removeBadSockets();
		}
	}
	
	private void sendGameFinishedMessage(){
		if(m_finishedPlayers.size()==m_serverSocket.getClients().size()){
			String[] playerNames=new String[m_serverSocket.getClients().size()];
			long[] durations=new long[m_serverSocket.getClients().size()];
			
			GameResultMessage message=new GameResultMessage(playerNames,durations);
			
			sendMessage(message);
			
			setChanged();
			notifyObservers(IObserverArguments.GAME_FINISHED);
		}
	}
	
	private void addFinishedPlayer(FinishedPlayer player){
		if(player!=null){
			synchronized(m_finishedPlayers){
				if(m_finishedPlayers.size()==0){
					m_finishedPlayers.add(player);
				}else{
					boolean sortedIn=false;
					
					for(int i=0; i<m_finishedPlayers.size(); i++){
						if(player.getTime()<m_finishedPlayers.get(i).getTime()){
							m_finishedPlayers.add(i,player);
							
							sortedIn=true;
							i=i+m_finishedPlayers.size();
						}
					}
					
					if(!sortedIn){
						m_finishedPlayers.add(player);
					}
				}
			}
			
			sendGameFinishedMessage();
		}
	}
	
	public FinishedPlayer[] getRanking(){
		return m_finishedPlayers.toArray(new FinishedPlayer[0]);
	}
	
	private void removeBadSockets(){
		synchronized(m_serverSocket.getClients()){
			int i=0;
			
			while(i<m_serverSocket.getClients().size()){
				if((m_serverSocket.getClients().get(i)==null) || ((m_serverSocket.getClients().get(i)!=null) &&
						(m_serverSocket.getClients().get(i).isAlive()==false))){
					m_serverSocket.getClients().remove(i);
				}else{
					i++;
				}
			}
		}
	}
	
	//interface implementation
	public String[] getPuzzleIds(){
		return m_selectPuzzleController.getPuzzleIds();
	}
	
	public void selectPuzzle(int index){
		if((index>=0) && (index<getPuzzleIds().length)){
			m_selectPuzzleController.selectPuzzle(index);
			
			//host logic
			m_finishedPlayers.clear();
			String puzzleId=getPuzzleIds()[index];
			
			m_playPuzzleController.loadPuzzle(puzzleId);
			m_playPuzzleController.shuffle();
			
			sendMessage(new StartPuzzleMessage(puzzleId,m_playPuzzleController.getStateAsArray(),
					m_playPuzzleController.getEndStateAsArray(),
					m_playPuzzleController.getEightPuzzle().getPuzzleBoard().getEmptyPart().getNumber()));
		}
	}
	
	public void cancelPuzzle(){
		m_playPuzzleController.cancelPuzzle();
	}
	
	public boolean isFinished(){
		boolean finished=m_playPuzzleController.isFinished();
		
		//host logic
		if(finished){
			addFinishedPlayer(new FinishedPlayer("The Host", getDuration()));
		}
		
		return finished;
	}
	
	public void loadPuzzle(String puzzleId){
		m_playPuzzleController.loadPuzzle(puzzleId);
	}
	
	public void shuffle(){
		m_playPuzzleController.shuffle();
	}
	
	public void start(){
		m_playPuzzleController.start();
		
		
	}
	
	public int[][] swapEmptyPart(SwapDirection swapDirection){
		return m_playPuzzleController.swapEmptyPart(swapDirection);
	}
	
	public int[][] swapPart(Position position){
		return m_playPuzzleController.swapPart(position);
	}
	
	public long getDuration(){
		return m_playPuzzleController.getDuration();
	}
	
	public int[][] getImagePositions(){
		return m_playPuzzleController.getImagePositions();
	}
	
	public void update(Observable observable, Object argument){
		if(observable instanceof NetworkServerSocket){
			if(argument.equals(IObserverArguments.CLIENT_JOINED)){
				setChanged();
				notifyObservers(IObserverArguments.CLIENT_JOINED);
			}else if(argument.equals(IObserverArguments.JOIN_SUCCESSFUL)){
				android.util.Log.d("MultiPlayerHostController","join successful");
			}
		}else if(observable instanceof NetworkSocket){
			if(argument instanceof PuzzleSolvedMessage){
				PuzzleSolvedMessage psm=(PuzzleSolvedMessage)argument;
				
				addFinishedPlayer(new FinishedPlayer(psm.getPlayerName(),psm.getSeconds()));
			}else if(argument instanceof JoinGameMessage){
				m_playerNames.add(((JoinGameMessage)argument).getPlayerName());
				
				setChanged();
				notifyObservers(IObserverArguments.CLIENT_JOINED);
			}
		}
	}
}
